home *** CD-ROM | disk | FTP | other *** search
/ CD/PC Actual 9 / CDACTUAL9.iso / share / Dos / VARIOS / pascal / SWAG9605.DDD / 0091_FAST Sort text file(s).pas < prev    next >
Encoding:
Pascal/Delphi Source File  |  1996-05-31  |  34.4 KB  |  999 lines

  1. (*
  2. Program TSort;
  3.    sort text file(s) in the current directory
  4.    sort inputfile(s) outputfile
  5.      case insensitive sort, (optional) skipping empty and duplicate lines
  6.  
  7.      inputfile(s) up to 248.
  8.        DOS wildcards supported for input files
  9.        input files are not altered
  10.        abort if file error input file (file does not exist, read error)
  11.  
  12.      outputfile:
  13.        if outputfile already does exist, it won't be sorted in memory,
  14.        but instead only file merged with the temporary files
  15.        ( so it has to be sorted already! ).
  16.  
  17.      setting DOS errorlevel to 0 on success, 1 if an error occurred.
  18.  
  19.    the more files to merge together, the slower the filemerge.
  20.    all textlines will be written to temporary files, so there must be
  21.    free disk space of at least the total size of the files to sort.
  22.  
  23.    if necessary, increase files= in config.sys and reboot,
  24.    or run Quarterdeck's files.com or a similar program
  25.    to increase the number of filehandles allowed by DOS
  26.    (max 99 for DOS 2.x; max 254 for DOS 3.x or later).
  27.  
  28.    Author: Eddy Thilleman, first version: september 1994
  29.    written in Borland Pascal version 7.01
  30.    Donated to the public domain. No rights reserved.
  31.  
  32.    You can reach me in the international Pascal conferences of Ilink, RIME
  33.    and Fidonet.
  34.  
  35.    modifications:
  36.      may 1995: Uppercase and compare integrated in one asm routine (CompUCStr)
  37.     june 1995: Upper routine removed (not used anymore)
  38.                a few (not used) string variables removed
  39.      aug 1995: more $DEFINE- and matching $IFDEF-directives added, so you
  40.                can easily adjust the program
  41.     sept 1995: Tsort can now eliminate duplicate lines if the file to be
  42.                sorted fits entirely in memory
  43.      feb 1996: added total input lines and total lines sorted in memory
  44.      may 1996: added the Release TimeSlice $DEFINE-directive
  45.  *)
  46.  
  47. {$A+,B-,D-,E-,F-,G-,I-,L-,N-,O-,P-,Q-,R-,S-,T-,V-,X-,Y-}
  48. {$M 16384,1024,655360}
  49.  
  50.  {$DEFINE ReleaseTimeSlice}
  51. { Release timeslices under a multitask environment, uses the DPMI call
  52.   works under OS/2 and Windows, disable it if you don't want that }
  53.  
  54.  {$DEFINE NoDupes}
  55. {  comment the above directive if you don't want to check for duplicate lines  }
  56.  
  57. (* {$DEFINE NoPlus} *)
  58. {  uncomment the above directive if you want to delete lines terminated by
  59.    '+' characters }
  60.  
  61.  {$DEFINE CheckLines}
  62. {  comment the above directive if you don't want to skip empty lines or
  63.    lines containing no chars >= ASCII 20h (=space)  }
  64.  
  65.  {$DEFINE Test_Lines}
  66. {  comment the above directive if you don't want to test lines for zero length or
  67.    containing no chars >= ASCII 20h or terminating '+' chars
  68.    NOTE: if NoPlus or CheckLines is defined, Test_Lines will automatically
  69.          be defined as well
  70.          (because NoPlus and CheckLines are nested inside Test_Lines) }
  71.  
  72. {$IFDEF NoPlus}             (* if NoPlus is defined *)
  73.   {$DEFINE Test_Lines}        (* Test_Lines must also be defined *)
  74. {$ENDIF}                               (*    *)
  75.  
  76. {$IFDEF CheckLines}         (* if CheckLines is defined *)
  77.   {$DEFINE Test_Lines}        (* Test_Lines must also be defined *)
  78. {$ENDIF}                               (*    *)
  79.         (* because NoPlus and CheckLines are nested inside Test_Lines *)
  80.  
  81.         (* nested directives compile fine, so you can now easily make
  82.            variations because of the directives and some of them can also
  83.            be combined, so I haven't tested all the variations or
  84.            combinations of them. Let me know if you encounter any problem,
  85.            so I can fix it. *)
  86.  
  87. Program TSort;
  88.  
  89. Uses
  90.   Dos;
  91.  
  92. const
  93.   NumbFiles=   254;
  94.   nr2div   = 10000;        { number to divide for counter on filemerge }
  95. type
  96.   fht      = array[1..NumbFiles] of byte;
  97. var
  98.   NewFHT   : fht;
  99.   OldFHT   : longint;
  100.   OldSize  : word;
  101.  
  102. Const
  103.   NoFAttr : word =   $1C;  { dir-, volume-, system attributen }
  104.   FAttr   : word =   $23;  { readonly-, hidden-, archive attributes }
  105.   MaxNrLines = 10000;  { max # lines to sort in memory in one run }
  106.   MaxNrFiles =   248;  { max 249 open files (248 temp. files + 1 dest.file) }
  107.   BufSize    =  8192;  { 8 KB for input- and output buffers }
  108.   SmallBufS  =  1024;  { 1 KB for input temp.files }
  109.  
  110. Type
  111.   String3   = String[ 3];
  112.   String12  = String[12];
  113.   LineStr   = String;
  114.   ptrLine   = ^LineStr;
  115.   BufType   = array [1..BufSize] of char;
  116.   SmallBufT = array [1..SmallBufS] of char;
  117.   tTxtFile  = record
  118.                 TxtFile  : text;
  119.                 Line     : string;
  120.                 EndOfFile: boolean;
  121.                 Error    : boolean;
  122.                 SmallBuf : SmallBufT;
  123.               end;
  124.   pTxtFile  = ^tTxtFile;
  125.  
  126. Const
  127.   WhiteSpace : string3 = #00#09#255;
  128.  
  129. Var
  130.   MarkPtr   : pointer;        { marks start of Heapmemory }
  131.   aPtrLines : array [1..MaxNrLines] of ptrLine;
  132.   aPtrFiles : array [1..MaxNrFiles] of pTxtFile;
  133.   Line0     : String;         { temporary line }
  134.   NrLine    : word;           { current # of line in memory }
  135.   NrLines   : word;           { number of lines in memory   }
  136.   InputFile : text;           { input file   }
  137.   OutputFile: text;           { output file  }
  138.   DestFile  : String;         { filename of destination file }
  139.   SourceBuf : BufType;        { source text buffer      }
  140.   DestBuf   : BufType;        { destination text buffer }
  141.   FR        : SearchRec;      { FileRecord              }
  142.   FMask     : String12;       { FileMask                }
  143.   TempDir   : String3;        { temporary directory     }
  144.   TempFile  : String;         { temporary output file   }
  145.   TempNr    : byte;           { for name temp. file     }
  146.   tNr,tMaxNr: byte;           { for name temp. file     }
  147.   Temp      : String3;        { name for temp. file     }
  148.   Exists    : boolean;
  149.   ParamNr   : byte;
  150.   OldExitProc : Pointer;
  151.   t         : ptrLine;
  152.   Ready     : boolean;
  153.   divisor   : word;           { divisor for showing # of lines merged
  154.                                 inversely proportional to # of files  }
  155.   fName     : string12;       { for padding filename }
  156.   display   : string[79];
  157.   number    : string[ 5];
  158.   TotalIn   : longint;        { total of inputlines }
  159.   TotalSort : longint;        { total of in-memory-sorted lines }
  160.   tel       : word;           { count var }
  161.  
  162.  
  163. procedure SetCursorOff; assembler;
  164. asm
  165.   mov  AH,$01
  166.   mov  CX,$2B0C
  167.   int  $10
  168. end;
  169.  
  170. procedure SetCursorOn; assembler;
  171. asm
  172.   mov  AH,$01
  173.   mov  CX,$0B0C
  174.   int  $10
  175. end;
  176.  
  177.  
  178. function HeapFunc( Size: word ): byte; far; assembler;
  179.   { return value of
  180.       0 : failure, run-time error, immediate abortion
  181.       1 : failure, New or GetMem returns a nil pointer
  182.       2 : success, retry
  183.     Borland Pascal Language Guide, page 265
  184.     "HeapError variable"
  185.   }
  186. asm
  187.   mov  ax, 1
  188. end  { HeapFunc };
  189.  
  190.  
  191. procedure MakeNewFHT;
  192.   { create a new expanded file handle table }
  193. begin
  194.   Oldsize := MemW[PrefixSeg:$32];            { Store the old FHT size     }
  195.   OldFHT  := MemL[PrefixSeg:$34];            { Store the old FHT address  }
  196.   FillChar(NewFHT,NumbFiles,$ff);            { Fill new table with 255    }
  197.   MemW[PrefixSeg:$32] := NumbFiles;          { Put new size in the psp    }
  198.   MemL[PrefixSeg:$34] := longint(@NewFHT);   { new FHT address in psp     }
  199.   move(Mem[PrefixSeg:$19],NewFHT,$15);       { put contents of old to new }
  200. end; { MakeNewFHT }
  201.  
  202.  
  203. function OpenTextFile( var InF: text; name: string; var buffer; size: word ): boolean;
  204. begin
  205.   Assign( InF, Name );
  206.   SetTextBuf( InF, buffer, size );
  207.   Reset( InF );
  208.   OpenTextFile := (IOResult = 0);
  209. end  { OpenTextFile };
  210.  
  211.  
  212. function CreateTextFile( var OutF: text; name: string; var buffer: BufType ): boolean;
  213. begin
  214.   Assign( OutF, Name );
  215.   SetTextBuf( OutF, buffer );
  216.   Rewrite( OutF );
  217.   CreateTextFile := (IOResult = 0);
  218. end  { CreateTextFile };
  219.  
  220.  
  221. function Exist( Name : string ) : Boolean;
  222.   { Return true if directory or file with the same name is found}
  223. var
  224.   F    : file;
  225.   Attr : Word;
  226. begin
  227.   Assign( F, Name );
  228.   GetFAttr( F, Attr );
  229.   Exist := (DosError = 0)
  230. end;
  231.  
  232.  
  233. function fExist( fName: string ) : boolean;
  234. begin
  235.   fExist := ( FSearch(fName,'') <> '' );
  236. end;
  237.  
  238.  
  239. procedure UniekeEntry( var Naam : string3 );
  240. const
  241.   min   = 128;
  242. var
  243.   Nbyte : array [0..3] of byte absolute Naam;
  244.   Exists : boolean;
  245.  
  246. begin
  247.   Nbyte [0] :=  3;  { filename of 3 characters }
  248.  
  249.   Exists := True;
  250.   Nbyte [1] := 255;
  251.   while (Nbyte [1] >= min) and Exists do
  252.   begin
  253.     Nbyte [2] := 255;
  254.     while (Nbyte [2] >= min) and Exists do
  255.     begin
  256.       Nbyte [3] := 255;
  257.       while (Nbyte [3] >= min) and Exists do
  258.       begin
  259.         Exists := Exist( Naam );
  260.         if Exists then dec (Nbyte [3]);
  261.       end;
  262.       if Exists then dec (Nbyte [2]);
  263.     end;
  264.     if Exists then dec (Nbyte [1]);
  265.   end;
  266. end;  { UniekeEntry }
  267.  
  268.  
  269. function fRename( var Source, Dest: string ): boolean; assembler;
  270.   { rename file or move file on same drive  }
  271.   { *no* error checking!                    }
  272.   { source and dest will be zero terminated }
  273.   { by adding the ASCII zero char to both   }
  274.   { so there must be room left for one char }
  275.   { but that is not checked                 }
  276.   { (byte length is not affected)           }
  277. asm     push  ds          { save ds                       }
  278.         xor   ax, ax      { clear ax                      }
  279.  
  280.         lds   si, source  { DS:SI = @source               }
  281.         mov   al, [si]    { load length byte              }
  282.         inc   si          { point to first char           }
  283.         mov   dx, si      { DS:DX = @source (for dos)     }
  284.         add   si, ax      { get beyond end of string      }
  285.         mov   [si], ah    { zero terminated string        }
  286.  
  287.         les   di, dest    { ES:DI = @dest                 }
  288.         mov   al, [di]    { load length byte              }
  289.         inc   di          { point to first char           }
  290.         mov   si, di      { ES:DI = @dest (for dos)       }
  291.         add   si, ax      { get beyond end of string      }
  292.         mov   [si], ah    { zero terminated string        }
  293.  
  294.         mov   ah, 56h     { dos function rename file      }
  295.         mov   cl, 23h     { file attribute mask           }
  296.         int   21h         { call dos to rename file       }
  297.  
  298.         mov   ax, 0       { assume false return value     }
  299.         jc    @exit       { error, return false           }
  300.         inc   ax          { return value true             }
  301. @exit:  pop   ds          { restore ds                    }
  302. end;  { fRename }
  303.  
  304.  
  305. procedure StrCopy( var Str1, Str2: string ); assembler;
  306.   { copy str1 to str2 }
  307. asm     mov   dx, ds      { save DS                       }
  308.         lds   si, str1    { load in DS:SI pointer to str1 }
  309.         cld               { string operations forward     }
  310.         les   di, str2    { load in ES:DI pointer to str2 }
  311.         xor   ch, ch      { clear CH                      }
  312.         mov   cl, [si]    { length str1 --> CX            }
  313.         inc   cx          { include length byte           }
  314.     rep movsb             { copy str1 to str2             }
  315. @exit:  mov   ds, dx      { finished, restore DS          }
  316. end  { StrCopy };
  317.  
  318.  
  319. procedure Byte2zStr( num, width: byte; var str: string ); assembler;
  320.   { Byte to string with leading zeros }
  321. asm
  322.         std                 { string operations backwards }
  323.         mov   al, [num]     { numeric value to convert    }
  324.         mov   cl, [width]   { width of str                }
  325.         xor   ch, ch        { clear ch                    }
  326.         jcxz  @exit         { done?                       }
  327.         les   di, str       { adress of str               }
  328.         mov   [di], cl      { length of str               }
  329.         add   di, cx        { start with last char str    }
  330. @start: aam                 { divide al by 10             }
  331.         add   al, 30h       { convert remainder to char   }
  332.         stosb               { store digit                 }
  333.         mov   al, ah        { move quotient to AL         }
  334.         dec   cl            { count down                  }
  335.         jcxz  @exit         { done?                       }
  336.         jmp   @start        { next digit                  }
  337. @exit:
  338. end  { Byte2zStr };
  339.  
  340.  
  341. function CompUCStr( var Str1, Str2: String ): ShortInt; Assembler;
  342.   { Compare Str1 and Str2 case insensitive }
  343. asm     mov   dx, ds                 { save ds                        }
  344.         lds   si, str1               { ds:si = @str1                  }
  345.         les   di, str2               { es:di = @str2                  }
  346.         cld                          { string operations forwards     }
  347.         lodsb                        { get length string1 in AL       }
  348.         mov   ah, es:[di]            { get length string2 in AH       }
  349.         inc   di
  350.         mov   bx, ax                 { save both lengths in BX        }
  351.         xor   cx, cx                 { clear cx                       }
  352.         mov   cl, al                 { get length String1 in CX       }
  353.         cmp   cl, ah                 { equal to length String2?       }
  354.         jb    @len                   { CX stores minimum length       }
  355.         mov   cl, ah                 { of string1 and string2         }
  356.   @len: jcxz  @exit                  { quit if null                   }
  357.  
  358.  @loop: lodsb                        { str1[i] in AL                  }
  359.         mov   ah, es:[di]            { str2[i] in AH                  }
  360.  
  361.         cmp   al, 'a'                { uppercase if 'a'..'z'          }
  362.         jb    @1
  363.         cmp   al, 'z'
  364.         ja    @1
  365.         sub   al, 20h
  366.  
  367.     @1: cmp   ah, 'a'                { uppercase if 'a'..'z'          }
  368.         jb    @2
  369.         cmp   ah, 'z'
  370.         ja    @2
  371.         sub   ah, 20h
  372.  
  373.     @2: cmp   al, ah                 { compare str1 to str2           }
  374.         jne   @not                   { loop if equal                  }
  375.         inc   di                     { next char str2                 }
  376.         dec   cx                     { countdown                      }
  377.         jcxz  @exit                  { strings same, Length also?     }
  378.         jmp   @loop                  { go do next char                }
  379.  
  380.   @not: mov   bx, ax                 { BL = AL = String1[i],
  381.                                        BH = AH = String2[i]           }
  382.  @exit: xor   ax, ax
  383.         cmp   bl, bh                 { length or contents comp        }
  384.         je    @equal                 { str1 = str2: return  0         }
  385.         jb    @lower                 { str1 < str2: return -1         }
  386.         inc   ax                     { str1 > str2: return  1         }
  387.         inc   ax
  388. @lower: dec   ax
  389. @equal: mov   ds, dx                 { restore ds                     }
  390. end   { CompUCStr };
  391.  
  392.  
  393. {$IFDEF Test_Lines}
  394. procedure White2Space( var Str: string; const WhiteSpace: string ); assembler;
  395.   { replace white space chars in Str by spaces
  396.     the string WhiteSpace contains the chars to replace }
  397. asm     push  ds                 { save DS                       }
  398.         cld                      { string operations forwards    }
  399.         les   di, str            { ES:DI points to Str           }
  400.         xor   cx, cx             { clear cx                      }
  401.         mov   cl, [di]           { length Str in cl              }
  402.         jcxz  @exit              { if length of Str = 0, exit    }
  403.         inc   di                 { point to 1st char of Str      }
  404.         mov   dx, cx             { store length of Str           }
  405.         mov   bx, di             { pointer to Str                }
  406.         lds   si, WhiteSpace     { DS:SI points to WhiteSpace    }
  407.         mov   ah, [si]           { load length of WhiteSpace     }
  408.  
  409. @start: cmp   ah, 0              { more chars WhiteSpace left?   }
  410.         jz    @exit              { no, exit                      }
  411.         inc   si                 { point to next char WhiteSpace }
  412.         mov   al, [si]           { next char to hunt             }
  413.         dec   ah                 { ah counting down              }
  414.         xor   dh, dh             { clear dh                      }
  415.         mov   cx, dx             { restore length of Str         }
  416.         mov   di, bx             { restore pointer to Str        }
  417.         mov   dh, ' '            { space char                    }
  418. @scan:
  419.   repne scasb                    { the hunt is on                }
  420.         jnz   @next              { white space found?            }
  421.         mov   [di-1], dh         { yes, replace that one         }
  422. @next:  jcxz  @start             { if no more chars in Str       }
  423.         jmp   @scan              { if more chars in Str          }
  424. @exit:  pop   ds                 { we are finished.              }
  425. end  { White2Space };
  426. {$ENDIF}
  427.  
  428.  
  429. {$IFDEF Test_Lines}
  430. procedure RTrim( var Str: string ); assembler;
  431.   { remove trailing spaces from str }
  432. asm     { setup }
  433.         std                      { string operations backwards   }
  434.         les   di, str            { ES:DI points to Str           }
  435.         xor   cx, cx             { clear cx                      }
  436.         mov   cl, [di]           { length Str in cl              }
  437.         jcxz  @exit              { if length of Str = 0, exit    }
  438.         mov   bx, di             { bx points to Str              }
  439.         add   di, cx             { start with last char in Str   }
  440.         mov   al, ' '            { hunt for spaces               }
  441.  
  442.         { remove trailing spaces }
  443.    repe scasb                    { the hunt is on                }
  444.         jz    @null              { only spaces?                  }
  445.         inc   cx                 { no, don't lose last char      }
  446. @null:  mov   [bx], cl           { overwrite length byte of Str  }
  447. @exit:
  448. end  { RTrim };
  449. {$ENDIF}
  450.  
  451. (*
  452. procedure LTrim( var Str: string ); assembler;
  453.   { remove leading white space from str }
  454. asm     push  ds                 { save DS                            }
  455.         cld                      { string operations forward          }
  456.         lds   si, str            { DS:SI points to Str                }
  457.         xor   cx, cx             { clear cx                           }
  458.         mov   cl, [si]           { length Str --> cl                  }
  459.         jcxz  @exit              { if length Str = 0, exit            }
  460.         mov   bx, si             { save pointer to length byte of Str }
  461.         inc   si                 { 1st char of Str                    }
  462.         mov   di, si             { pointer to 1st char of Str --> di  }
  463.         mov   al, ' '            { hunt for spaces                    }
  464.         xor   dx, dx             { clear dx                           }
  465.  
  466.         { look for leading spaces }
  467.    repe scasb                    { the hunt is on                     }
  468.         jz    @done              { if only spaces, we are done        }
  469.         inc   cx                 { no, don't lose 1st non-blank char  }
  470.         dec   di                 { no, don't lose 1st non-blank char  }
  471.         mov   dx, cx             { new lenght of Str                  }
  472.         xchg  di, si             { swap si and di                     }
  473.     rep movsb                    { move remaining part of Str         }
  474. @done:  mov   [bx], dl           { new length of Str                  }
  475. @exit:  pop   ds                 { finished, restore DS               }
  476. end  { LTrim };
  477. *)
  478.  
  479. procedure Pad( var Str: String; len: byte ); assembler;
  480.   { pad str with spaces while length str < len }
  481.   { len must not be greater than size( str )   }
  482.   { this is not checked!                       }
  483. asm
  484.              les   di, str            { ES:DI = @str               }
  485.              cld                      { string operations forward  }
  486.              xor   ax, ax             { clear ax                   }
  487.              mov   al, [di]           { load length byte in al     }
  488.              and   al, al             { length str = 0?            }
  489.              jz    @exit              { yes, done                  }
  490.  
  491.              xor   cx, cx             { clear cx                   }
  492.              mov   cl, len            { load new length            }
  493.              mov   bl, cl             { store new length           }
  494.              sub   cl, al             { len - length str           }
  495.              jna   @exit              { length str >= len          }
  496.  
  497.              mov   [di], bl           { set new length             }
  498.              add   di, ax             { get to end of str          }
  499.              inc   di                 { get beyond end of str      }
  500.              mov   ax, '  '           { fill with spaces           }
  501.              shr   cx, 1              { (len-length) / 2           }
  502.              jnc   @pad               { if (len-lenght) even, pad  }
  503.              mov   [di], al           { if odd # of spaces to fill }
  504.              jcxz  @exit              { if only one space, exit    }
  505.              inc   di                 { next destination           }
  506.    @pad: rep stosw                    { pad with spaces            }
  507.   @exit:
  508. end; { Pad }
  509.  
  510.  
  511. {$IFDEF CheckLines}
  512. function LineOK( var str: string ) : Boolean; assembler;
  513.   { Line contains chars > ASCII 20h ? }
  514. asm     mov   dx, ds         { save DS                          }
  515.         xor   ax, ax         { assume false return value        }
  516.         xor   cx, cx         { clear cx                         }
  517.         lds   si, str        { load in DS:SI pointer to Str     }
  518.         mov   cl, [si]       { length Str --> cx                }
  519.         jcxz  @exit          { if no characters, exit           }
  520.         inc   si             { point to 1st character           }
  521.  
  522.         { look for chars > ASCII 20h }
  523. @start: mov   bl, [si]       { load character                   }
  524.         cmp   bl, ' '        { char > ASCII 20h?                }
  525.         ja    @yes           { yes, return true                 }
  526.         inc   si             { next character                   }
  527.         dec   cx             { count down                       }
  528.         jcxz  @exit          { if no more characters left, exit }
  529.         jmp   @start         { try again                        }
  530. @yes:   mov   ax, 1          { return value true                }
  531. @exit:  mov   ds, dx         { restore DS                       }
  532. end  { LineOK };
  533. {$ENDIF}
  534.  
  535.  
  536. procedure Sorting( min, max: word );
  537. var
  538.   n : byte;
  539.   x : longint;
  540.  
  541.   {$S+}
  542.   function IsLess( i1, i2: word ): boolean;
  543.   begin
  544.     IsLess := (CompUCStr( aPtrLines[i1]^, aPtrLines[i2]^ ) < 0);
  545.   end;
  546.  
  547.   procedure QuickSort( left, right: word );
  548.     { Case insensitive QuickSort }
  549.   var
  550.     lower, upper, middle: word;
  551.   begin
  552.     lower  := left;
  553.     upper  := right;
  554.     middle := (left+right) div 2;
  555.     repeat
  556.       while IsLess( lower , middle ) do inc( lower );
  557.       while IsLess( middle, upper  ) do dec( upper );
  558.       if lower <= upper then
  559.       begin
  560.         { swap pointers }
  561.         t := aPtrLines[lower];
  562.         aPtrLines[lower] := aPtrLines[upper];
  563.         aPtrLines[upper] := t;
  564.         inc( lower );
  565.         dec( upper );
  566.       end;
  567.     until lower > upper;
  568.     if left  < upper then QuickSort( left , upper );
  569.     if lower < right then QuickSort( lower, right );
  570.   end  { QuickSort };
  571.   {$S-}
  572.  
  573.   function Sorted: boolean;
  574.   Var
  575.     i: word;
  576.   begin
  577.     Sorted := True;
  578.     x := 0;
  579.     For i := 1 to Pred( Max ) do
  580.       if IsLess( Succ( i ), i ) then
  581.       begin
  582.         Sorted := False;
  583.         inc( x );
  584.       end;
  585.     { end for i loop }
  586.   end;
  587.  
  588. begin  { Sorting }
  589.   n := 0;
  590.   Str( NrLines:5, number );
  591.   display := fName + ':' + Temp + '  ' + number + ' lines   Sorting ';
  592.   while not Sorted do
  593.   begin
  594.     write( #13, display, n:5,' ',x:5 );
  595.     inc( n );
  596.     QuickSort( min, max );
  597.   end;
  598.   write( #13, display, n:5,' ',x:5 );
  599. end;  { Sorting }
  600.  
  601.  
  602. {$IFDEF Test_Lines}
  603. procedure TestLines;
  604. var
  605.   i   : word;
  606.   len : byte;
  607.  
  608.   procedure TrimLine;
  609.   begin
  610.     White2Space( aPtrLines[i]^, WhiteSpace );  { white space to spaces   }
  611.     RTrim( aPtrLines[i]^ );                    { remove trailing spaces  }
  612.     len := length( aPtrLines[i]^ );
  613.   end;
  614.  
  615. {$IFDEF NoPlus}
  616.   procedure TrimPlus;
  617.   begin
  618.     TrimLine;
  619.     while aPtrLines[i]^[len] = '+' do
  620.     begin
  621.       dec( len );
  622.       aPtrLines[i]^[0] := chr( len );
  623.       TrimLine;
  624.     end;
  625.   end;
  626. {$ENDIF}
  627.  
  628. begin
  629.   for i := 1 to NrLines do
  630.   begin
  631.     len := length( aPtrLines[i]^ );
  632. {$IFDEF NoPlus}
  633.     TrimPlus;
  634. {$ELSE}
  635.     TrimLine;
  636. {$ENDIF}
  637. {$IFDEF CheckLines}
  638.     if ((len = 0) or not LineOK( aPtrLines[i]^ )) then
  639.       aPtrLines[i] := nil;             { invalid Line }
  640. {$ENDIF}
  641.   end;
  642. end;  { TestLine }
  643. {$ENDIF}
  644.  
  645.  
  646. procedure Process( var SourceFile : string12 );
  647.  
  648. {$IFDEF NoDupes}
  649.   function IsEqual( i1, i2: word ): boolean;
  650.   begin
  651.     IsEqual := (CompUCStr( aPtrLines[i1]^, aPtrLines[i2]^ ) = 0);
  652.   end;
  653. {$ENDIF}
  654.  
  655. begin
  656.   if OpenTextFile( InputFile, SourceFile, SourceBuf, BufSize ) then
  657.   begin
  658.     while not EOF( InputFile ) and (IOResult = 0) do
  659.     begin
  660.       inc( TempNr );
  661.       Byte2zStr( TempNr, 3, Temp );
  662.       TempFile := TempDir + '\' + Temp;
  663.       write( fName, ':', Temp, '  ' );
  664.       if CreateTextFile( OutputFile, TempFile, DestBuf ) then
  665.       begin
  666.         { read lines from input files }
  667.         Mark( MarkPtr );
  668.         NrLine := 1;
  669.         if (Length( Line0 ) = 0) then ReadLn( InputFile, Line0 );
  670.         GetMem( aPtrLines[NrLine], Length( Line0 ) + 1 );
  671.  
  672.         while not EOF(InputFile) and (IOResult = 0)
  673.             and (NrLine <= MaxNrLines) and (aPtrLines[NrLine] <> nil) do
  674.         begin
  675.           StrCopy( Line0, aPtrLines[NrLine]^ );
  676.           ReadLn( InputFile, Line0 );
  677.           Inc( NrLine );
  678.           if (NrLine <= MaxNrLines) then
  679.             GetMem( aPtrLines[NrLine], Length( Line0 )+1 );
  680.         end; { while not memory full }
  681.  
  682.         if ((NrLine <= MaxNrLines) and (aPtrLines[NrLine] <> nil)) then
  683.         begin
  684.           if EOF(InputFile) then
  685.           begin
  686.             aPtrLines[NrLine]^ := Line0;
  687.             Line0 := '';
  688.           end;
  689.         end
  690.         else
  691.           Dec( NrLine );
  692.         NrLines := NrLine;
  693.         TotalIn := TotalIn + NrLine;
  694.         Write( NrLines:5, ' lines' );
  695.  
  696. {$IFDEF Test_Lines}
  697.         { Test / Trim Lines }
  698.         TestLines;
  699. {$ENDIF}
  700.  
  701.         { sort pointers }
  702.         Sorting( 1, NrLines );
  703.  
  704. {$IFDEF NoDupes}
  705.         tel := 1;
  706.         NrLine := 1;
  707.         while NrLine < NrLines do
  708.         begin
  709.           if IsEqual( NrLine, NrLine+1 ) then
  710.           begin
  711.             while IsEqual( NrLine, NrLine+tel ) do
  712.             begin
  713.               aPtrLines[NrLine+tel] := nil;             { eliminate dupe }
  714.               inc( tel );
  715.             end;
  716.           end;
  717.           inc( NrLine, tel );
  718.           tel := 1;
  719.         end;
  720. {$ENDIF}
  721.  
  722.         { write sorted lines in temp files }
  723.         tel := 0;
  724.         for NrLine := 1 to NrLines do
  725.         begin
  726.           if (aPtrLines[NrLine] <> nil) then
  727.           begin
  728.             Writeln( OutputFile, aPtrLines[NrLine]^ );
  729.             inc( tel );
  730.           end;
  731.           if (IOResult <> 0) then
  732.           begin
  733.             writeln( 'Error writing ', TempFile );
  734.             halt( 1 );
  735.           end;
  736.           aPtrLines[NrLine]^ := '';
  737.           aPtrLines[NrLine] := nil;
  738.         end;
  739.         writeln( '   ', tel:5, ' lines' );
  740.         TotalSort := TotalSort + Tel;
  741.         Release( MarkPtr );
  742.         Close( OutputFile );
  743.       end  { if CreateTextFile }
  744.       else
  745.       begin
  746.         writeln(' error creating file ', TempFile );
  747.         Halt( 1 );
  748.       end;  {if CreateTextFile }
  749.     end;  {while not eof}
  750.     Close( InputFile );
  751.   end   { if OpenTextFile }
  752.   else
  753.     writeln(' error opening file ', SourceFile );
  754.   { endif OpenTextFile }
  755. {$IFDEF ReleaseTimeSlice}
  756.   asm                { release time slice }
  757.     mov   AX,$1680
  758.     int   $2F
  759.   end;
  760. {$ENDIF}
  761. end  { Sorting };
  762.  
  763.  
  764. procedure MergeSort;
  765. var nr: byte;
  766.     count: longint;
  767.  
  768. {$IFDEF NoDupes}
  769.   function IsEqual( i1, i2: word ): boolean;
  770.   begin
  771.     IsEqual := (CompUCStr( aPtrFiles[i1]^.Line, aPtrFiles[i2]^.Line ) = 0);
  772.   end;
  773. {$ENDIF}
  774.  
  775.   function IsLess( i1, i2: word ): boolean;
  776.   begin
  777.     IsLess := (CompUCStr( aPtrFiles[i1]^.Line, aPtrFiles[i2]^.Line ) < 0);
  778.   end;
  779.  
  780. begin
  781.   tNr := 1;
  782.   tMaxNr := TempNr;
  783.   if TempNr > MaxNrFiles then tMaxNr := MaxNrFiles;
  784.   Mark( MarkPtr );
  785.  
  786.   New( aPtrFiles[tNr] );
  787.   while (tNr < tMaxNr) and (aPtrFiles[tNr] <> nil) do
  788.   begin
  789.     Inc( tNr );
  790.     New( aPtrFiles[tNr] );
  791.   end;
  792.   if (aPtrFiles[tNr] = nil) then dec( tNr );
  793.  
  794.   tMaxNr := tNr;
  795.   for tNr := 1 to tMaxNr do    { open temp files and read first line }
  796.   begin
  797.     Byte2zStr( tNr, 3, Temp );
  798.     TempFile := TempDir + '\' + Temp;
  799.     if not OpenTextFile( aPtrFiles[tNr]^.TxtFile, TempFile, aPtrFiles[tNr]^.SmallBuf, SmallBufS ) then
  800.     begin
  801.       writeln( 'Error opening ', TempFile );
  802.       halt( 1 );
  803.     end;
  804.     ReadLn( aPtrFiles[tNr]^.TxtFile, aPtrFiles[tNr]^.Line );
  805.     if (IOResult <> 0) then
  806.     begin
  807.       writeln( 'Error reading ', TempFile );
  808.       halt( 1 );
  809.     end;
  810.     aPtrFiles[tNr]^.EndOfFile := EOF( aPtrFiles[tNr]^.TxtFile );
  811.     aPtrFiles[tNr]^.Error := (IOResult <> 0);
  812.   end;
  813.   divisor := (nr2div div tMaxNr);
  814.  
  815.   if CreateTextFile( OutputFile, DestFile, DestBuf ) then
  816.   begin
  817.     count := 0;
  818.     nr := 1;
  819.     Ready := False;
  820.     while not Ready do
  821.     begin
  822.       for tNr := 1 to tMaxNr do      { take alphabetically the first line }
  823.       begin
  824.         if tNr <> nr then
  825.         begin
  826.           if Length( aPtrFiles[tNr]^.Line ) > 0 then
  827.           begin
  828. {$IFDEF NoDupes}
  829.             while IsEqual( tNr, nr )
  830.             and not aPtrFiles[tNr]^.EndOfFile
  831.             and not aPtrFiles[tNr]^.Error
  832.             do     { no duplicates }
  833.             begin
  834.               ReadLn( aPtrFiles[tNr]^.TxtFile, aPtrFiles[tNr]^.Line );
  835.               aPtrFiles[tNr]^.Error := (IOResult <> 0);
  836.               aPtrFiles[tNr]^.EndOfFile := EOF( aPtrFiles[tNr]^.TxtFile );
  837.             end;
  838. {$ENDIF}
  839.             if IsLess( tNr, nr ) then
  840.               nr := tNr;
  841.           end;  { if Length( aPtrFiles[tNr]^.Line ) > 0 }
  842.         end;  { if tNr <> nr }
  843.       end;  { for tNr := 1 to tMaxNr loop }
  844.  
  845.       if Length( aPtrFiles[nr]^.Line ) > 0 then
  846.       begin
  847. {$IFDEF NoDupes}
  848.         if (CompUCStr( aPtrFiles[nr]^.Line, Line0 ) <> 0) then
  849.         begin
  850. {$ENDIF}
  851.           writeln( OutputFile, aPtrFiles[nr]^.Line );
  852.           if (IOResult <> 0) then
  853.           begin
  854.             writeln( 'Error writing ', DestFile );
  855.             halt( 1 );
  856.           end;
  857.           inc( count );
  858.           if (count mod divisor) = 0 then write( #13,'Merging ', count:7 );
  859. {$IFDEF ReleaseTimeSlice}
  860.           if (count mod 10000) = 0 then
  861.             asm                { release time slice }
  862.               mov   AX,$1680
  863.               int   $2F
  864.             end;
  865. {$ENDIF}
  866. {$IFDEF NoDupes}
  867.         end;
  868. {$ENDIF}
  869.         StrCopy( aPtrFiles[nr]^.Line, Line0 );       { last written line }
  870.         aPtrFiles[nr]^.Line := '';
  871.       end;
  872.  
  873.       while (not aPtrFiles[nr]^.EndOfFile and not aPtrFiles[nr]^.Error)
  874.       and (
  875. {$IFDEF NoDupes}
  876.       (CompUCStr( aPtrFiles[nr]^.Line, Line0 ) = 0) or
  877. {$ENDIF}
  878.       (Length( aPtrFiles[nr]^.Line ) = 0)) do
  879.       begin
  880.         ReadLn( aPtrFiles[nr]^.TxtFile, aPtrFiles[nr]^.Line );
  881.         aPtrFiles[nr]^.Error := (IOResult <> 0);
  882.         aPtrFiles[nr]^.EndOfFile := EOF( aPtrFiles[nr]^.TxtFile );
  883.       end;
  884.  
  885.       if Length( aPtrFiles[nr]^.Line ) = 0 then
  886.       begin
  887.         tNr := 1;        { the first non-empty line }
  888.         while Length( aPtrFiles[tNr]^.Line ) = 0 do inc( tNr );
  889.         if (tNr <= tMaxNr) then nr := tNr;
  890.       end;
  891.  
  892.       Ready := True;
  893.       tNr := 1;
  894.       while (tNr <= tMaxNr) and Ready do         { check for more lines }
  895.       begin
  896.         if (Length( aPtrFiles[tNr]^.Line ) > 0) then Ready := False;
  897.         inc( tNr );
  898.       end;
  899.     end;  { while not Ready }
  900.     Close( OutputFile );
  901.     Writeln( #13,'Merged ', count:7, ' lines' );
  902.   end;  { if CreateTextFile }
  903.  
  904.   for tNr := 1 to tMaxNr do
  905.   begin
  906.     Close( aPtrFiles[tNr]^.TxtFile );      { close and delete all temp files }
  907.     Erase( aPtrFiles[tNr]^.TxtFile );
  908.   end;
  909.   Release( MarkPtr );
  910. end  { MergeSort };
  911.  
  912.  
  913. {$F+}
  914. procedure OurExitProc;
  915. begin
  916.   ExitProc := OldExitProc;
  917.  
  918.   { Restore Old File Handle Table }
  919.   MemW[PrefixSeg:$32] := OldSize;
  920.   MemL[PrefixSeg:$34] := OldFHT;
  921.  
  922.   SetCursorOn;
  923. end;
  924. {$F-}
  925.  
  926.  
  927. begin
  928.   {set up our exit handler}
  929.  
  930.   OldExitProc := ExitProc;
  931.   ExitProc := @OurExitProc;
  932.  
  933.   if ParamCount > 1 then           { parameters: inputfile(s) outputfile }
  934.   begin
  935.     SetCursorOff;
  936.     TotalIn   := 0;
  937.     TotalSort := 0;
  938.     Line0 := '';
  939.     UniekeEntry( TempDir );
  940.     if not Exists then
  941.     begin
  942.       MkDir( TempDir );
  943.       if (IOResult=0) then
  944.       begin
  945.         HeapError := @HeapFunc;
  946.         DestFile := ParamStr( ParamCount );
  947.         TempNr := 0;
  948.  
  949.         if fExist( DestFile ) then
  950.         begin                              { if outputfile already exist }
  951.           inc( TempNr );
  952.           Byte2zStr( TempNr, 3, Temp );
  953.           TempFile := TempDir + '\' + Temp;   { move it to the temp directory }
  954.           if fRename( DestFile, TempFile ) then
  955.             writeln( DestFile, ':', Temp, '  ' )
  956.           else
  957.             dec( TempNr );
  958.         end;  { if fExist( DestFile ) }
  959.  
  960.         for ParamNr := 1 to (ParamCount-1) do         { all inputfile(s) }
  961.         begin
  962.           FMask := ParamStr( ParamNr );               { filemask         }
  963.           FindFirst(FMask, FAttr, FR);
  964.           while DosError = 0 do
  965.           begin
  966.             StrCopy( FR.Name, fName );
  967.             Pad( fName, 12 );
  968.             Process( FR.Name );
  969.             FindNext( FR );
  970.           end;
  971.         end;  { all inputfile(s) }
  972.         writeln( 'Total: input ', TotalIn, ', sorted ', TotalSort, ' lines   ' );
  973.  
  974.         { if one temp file rename it to destination, else merge sort }
  975.         if TempNr = 1 then
  976.         begin
  977.           Byte2zStr( TempNr, 3, Temp );
  978.           TempFile := TempDir + '\' + Temp;
  979.           if not fRename( TempFile, DestFile ) then
  980.             writeln( 'Could not rename ',TempFile,' to ',DestFile );
  981.           {}
  982.         end
  983.         else
  984.         begin
  985.           MakeNewFHT;
  986.           MergeSort;
  987.         end;
  988.         RmDir( TempDir );     { remove temporary directory }
  989.       end   { if IOResult=0 }
  990.       else
  991.         writeln( 'Cannot create temporary directory!' );
  992.       { }
  993.     end;  { if not Exists TempDir }
  994.   end   { if ParamCount > 1 }
  995.   else
  996.     WriteLn( 'Sort inputfile(s) outputfile ' );
  997.   { }
  998. end.
  999.